package com.whty.demo.encrypt;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.persistence.Id;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * 和Encrypt注解相关的工具类
 * @author suren
 * @since jdk1.6
 * 2015年11月25日
 */
public class ReflectUtil{

    private static final Logger LOGGER = LoggerFactory.getLogger(ReflectUtil.class);
    private static final String DEF_KEY = "kEHrDooxWHCWtfeSxvDvgqZq";
    private static final Properties CONF_PRO;
    
    static {
        //读取配置文件
        InputStream configStream = null;
        CONF_PRO = new Properties();
        try {
            configStream = ReflectUtil.class.getClassLoader().getResourceAsStream("encrypt.properties");
            CONF_PRO.load(configStream);
        } catch (Exception e) {
            LOGGER.error("read data-encrypt.properties error.", e);
        }finally{
            if(configStream != null){
                try {
                    configStream.close();
                } catch (IOException e) {
                    LOGGER.error("close data-encrypt.properties error.", e);
                }
            }
        }
    }
    
    /**
     * 获取带有Encrypt注解的属性列表
     * @param clazz
     * @return if exception happend, return empty list.
     */
    public static List<Field> getEncryptFielsFromClazz(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<Field>();
        
        Field[] fields = clazz.getDeclaredFields();
        if(fields != null){
            for(Field field : fields){
                if(canNotEncrypt(field)){
                    continue;
                }
                
                Encrypt encrypt = field.getAnnotation(Encrypt.class);
                if(encrypt != null){
                    fieldList.add(field);
                }
            }
        }
        
        return fieldList;
    }
    
    /**
     * @see #getEncryptFielsFromClazz(Class)
     * @param obj
     * @return
     * @throws NullPointerException if the given obj is null
     */
    public static List<Field> getEncryptFileds(Object obj){
        if(obj == null){
            throw new NullPointerException();
        }
        
        return getEncryptFielsFromClazz(obj.getClass());
    }
    
    /**
     * 查找属性对应的setter方法
     * @param field
     * @param obj
     * @return
     * @throws NullPointerException if the field or obj is null
     */
    public static Method getSetter(Field field, Object obj){
        if(field == null || obj == null){
            throw new NullPointerException();
        }
        
        String fieldName = field.getName();
        fieldName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        fieldName = "set" + fieldName;
        
        try{
            return obj.getClass().getMethod(fieldName, field.getType());
        }catch (NoSuchMethodException e){
            e.printStackTrace();
        }catch (SecurityException e){
            e.printStackTrace();
        }
        
        return null;
    }
    
    /**
     * 查找属性对应的getter方法
     * @param field
     * @param obj
     * @return
     * @throws NullPointerException if the field or obj is null
     */
    public static Method getGetter(Field field, Object obj){
        if(field == null || obj == null){
            throw new NullPointerException();
        }
        
        String fieldName = field.getName();
        fieldName = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        fieldName = "get" + fieldName;
        
        try{
            return obj.getClass().getMethod(fieldName);
        }catch (NoSuchMethodException e){
            e.printStackTrace();
        }catch (SecurityException e){
            e.printStackTrace();
        }
        
        return null;
    }

    /**
     * 加密，加密后的密文前后有会中括号（[]）包括
     * @param field
     * @param entity
     * @return 属性不支持加密返回null
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static Object encrypt(Field field, Object entity)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        if(canNotEncrypt(field)){
            return null;
        }
        
        Method getterMethod = ReflectUtil.getGetter(field, entity);
        Object value = getterMethod.invoke(entity);
        if(value != null){
            String key = findKey(field);
            
            LOGGER.info("encrypt key:"+key);
            
            String encryptStr = DESUtil.encrypt(value.toString(), key);
            encryptStr = ("[" + encryptStr + "]");
            
            return encryptStr;
        }
        
        return null;
    }

    /**
     * 解密
     * @param field
     * @param entity
     * @return
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public static Object decrypt(Field field, Object entity)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
    	
        if(canNotEncrypt(field)){
            return null;
        }
        
        Method getterMethod = ReflectUtil.getGetter(field, entity);
        Object value = getterMethod.invoke(entity);
        if(value != null){
            //对于有加密标记的值才会进行解密
            if(ReflectUtil.hasEncryptMark(value.toString())){
            	String source = value.toString();
            	source = source.substring(1, source.length()-1);
                String key = findKey(field);
                System.out.println("decrypt key:"+key);
                return new String(DESUtil.decrypt(source, key));
                
            }else{
                return value;
            }
        }
        
        return null;
    }

    /**
     * 从注解中读取密钥key，然后在配置文件.properties文件中查找对应的密钥值，如果找不到就使用默认的<code>DEF_KEY</code>
     * @param field
     * @return
     */
    public static String findKey(Field field) {
        Encrypt annotation = field.getAnnotation(Encrypt.class);
        String key = DEF_KEY;
        
        if(annotation != null){
            String keyIndex = annotation.keyIndex();
            if(CONF_PRO.containsKey(keyIndex)){
                key = CONF_PRO.getProperty(keyIndex, DEF_KEY);
            }
        }
        
        return key;
    }
    
    /**
     * 判断是否支持加密
     * @param field
     * @return 支持加密返回true
     */
    public static boolean canEncrypt(Field field){
        return (String.class.equals(field.getType()));
    }
    
    /**
     * 判断是否支持加密
     * @param field
     * @return 不支持加密返回true
     */
    public static boolean canNotEncrypt(Field field){
        return !canEncrypt(field);
    }
    
    /**
     * 判断是否有需要加密的注解
     * @param field
     * @return 有加密注解返回true
     */
    public static boolean hasEncryptAnnotation(Field field){
        Encrypt encryptAnnotation = field.getAnnotation(Encrypt.class);
        
        return (encryptAnnotation != null);
    }
    
    /**
     * 判断密文中是否有加密标记（使用中括号[]将加密后的字符串包括起来）
     * @param encryptStr
     * @return
     */
    public static boolean hasEncryptMark(String encryptStr) {
        if(encryptStr != null && encryptStr.length() > 2) {
            return (encryptStr.startsWith("[") && encryptStr.endsWith("]"));
        }
        
        return false;
    }
    
    /** 
     * 获取实体类的字段信息 
     * @param clazz 实体类 
     * @return 字段集合 
     */  
    public static Field getIdField(Class<?> clazz){  
        Field[] fields = clazz.getDeclaredFields();  
        Field item = null;  
        for (Field field : fields) {  
            //获取实体类中标识@Id的字段  
            Id id = field.getAnnotation(Id.class);  
            if (id != null) {  
                field.setAccessible(true);  
                item = field;  
                break;  
            }  
        } 
        
        if(item==null){  
            Class<?> superclass = clazz.getSuperclass();  
            if(superclass!=null){  
                item = getIdField(superclass);  
            }  
        }  
        return item;  
    }  
}
